package com.example.shop.user.service.impl;

import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponseBody;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.shop.common.api.ServiceStateCode;
import com.example.shop.common.dto.mail.TemplateMail;

import com.example.shop.common.dto.sms.ShortMessage;
import com.example.shop.common.service.MailService;
import com.example.shop.common.service.SMSService;
import com.example.shop.common.util.FileOptionUtil;
import com.example.shop.goods.dao.GoodsTypeFondRatioDao;
import com.example.shop.goods.dto.GoodsTypeFondRatio;
import com.example.shop.mapper.UserMapper;
import com.example.shop.pojo.User;
import com.example.shop.common.util.CachedThreadPoolUtil;
import com.example.shop.rabbitmq.component.MerchantMessageComponent;
import com.example.shop.rabbitmq.component.UserMessageComponent;
import com.example.shop.redis.service.LettuceClient;
import com.example.shop.user.dao.UserPersonalInfoParamDao;
import com.example.shop.user.dto.RegisterParam;
import com.example.shop.common.api.ServiceReturn;
import com.example.shop.redis.enums.RedisKey;
import com.example.shop.user.dto.UserPersonalInfoParam;
import com.example.shop.user.dto.UserQueryParam;
import com.example.shop.user.enums.UserState;
import com.example.shop.user.service.UserService;
import com.example.shop.common.dto.face.FaceRecognitionReturnResult;
import com.example.shop.common.service.FaceRecognitionService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;

@Service
public class UserServiceImpl implements UserService {
    private final FaceRecognitionService faceRecognitionService;
    private final MailService mailService;
    private final SMSService smsService;
    private final UserMapper userMapper;
    private final LettuceClient redisClient;
    private final UserPersonalInfoParamDao userPersonalInfoParamDao;
    private final GoodsTypeFondRatioDao fondRatioDao;
    private final RabbitTemplate rabbitTemplate;

    public UserServiceImpl(FaceRecognitionService faceRecognitionService, MailService mailService, SMSService smsService, UserMapper userMapper, LettuceClient lettuceClient, UserPersonalInfoParamDao userPersonalInfoParamDao, GoodsTypeFondRatioDao fondRatioDao, RabbitTemplate rabbitTemplate) {
        this.faceRecognitionService = faceRecognitionService;
        this.mailService = mailService;
        this.smsService = smsService;
        this.userMapper = userMapper;
        this.redisClient = lettuceClient;
        this.userPersonalInfoParamDao = userPersonalInfoParamDao;
        this.fondRatioDao = fondRatioDao;
        this.rabbitTemplate = rabbitTemplate;
    }

    @Override
    public ServiceReturn<Boolean> checkRegisterParam(RegisterParam registerParam) {

        MultipartFile avatarFile = registerParam.getAvatarFile();
        if (avatarFile!=null) {
            boolean legalPicture = avatarFile.getOriginalFilename().matches("/^[*.jpg|*.JPG|*.PNG|*.png]$/");
            if(legalPicture){
                return new ServiceReturn<>(ServiceStateCode.IMAGE_ERROR_UNSUPPORTED_FORMAT,false);
            }
        }
        User cacheUser =  redisClient.getValueAsKey(RedisKey.USER_ID +registerParam.getUserAccount(),RedisKey.USER_INFO.getKey(),User.class);
        if(cacheUser==null){
            QueryWrapper<User> query = new QueryWrapper<>();
            query.eq("user_account",registerParam.getUserAccount());
            cacheUser = userMapper.selectOne(query);
            //如果查找出来的用户为空，则说明参数符合要求，即账户唯一
        }
        if(cacheUser!=null){
            //用户账户已经被注册过了
            return new ServiceReturn<>(ServiceStateCode.ACCOUNT_HAS_BEEN_REGISTER,false);
        }

        MultipartFile faceMulFile = registerParam.getFaceFile();
        //开启人脸识别
        if(faceMulFile!=null){
            try {
                String faceBase64 = Base64.getEncoder().encodeToString(faceMulFile.getBytes());
                int faceNum = faceRecognitionService.detectFaceNum(faceBase64);
                if(faceNum == -1){
                    return new ServiceReturn<>(ServiceStateCode.FACE_PICTURE_IS_NOT_PERSONAL,false);
                }
                if(faceNum!=1){
                    return new ServiceReturn<>(ServiceStateCode.FACE_PICTURE_IS_NOT_PERSONAL,false);
                }
                FaceRecognitionReturnResult result = faceRecognitionService.faceRecognition(faceBase64);
                if(result!=null && result.getConfidence()>=70){
                    return new ServiceReturn<>(ServiceStateCode.PERSON_HAS_ALREADY_REGISTER,false);
                }
            } catch (IOException e) {
                e.printStackTrace();
                return new ServiceReturn<>(ServiceStateCode.IMAGE_ERROR_UNSUPPORTED_FORMAT,false);
            }
        }
        return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
    }

    @Transactional(transactionManager="transactionManager",propagation = Propagation.REQUIRED)
    @Override
    public ServiceReturn<Boolean> register(RegisterParam registerParam) {
        User user = registerParam.toUser();
        String userResourceDir = user.getUserResourceDir();
        //采用固定头像名称,方便更换头像
        String fileName = "avatar.png";
        //保存后，将该头像资源路径保存到用户信息中
        user.setUserAvatar(userResourceDir+fileName);

        //如果头像图片存在，则将其读取出来并将保存到该用户资源目录下，否则保存默认头像
        MultipartFile avatarMulFile = registerParam.getAvatarFile();
        if(avatarMulFile!=null){
            //异步处理头像，即将其文件保存到该用户资源目录下
            try {
                FileOptionUtil.asyncWrite(user.getUserAvatar(),avatarMulFile.getBytes());
            } catch (IOException e) {
                throw new RuntimeException("User模块的UserServiceImpl的register文件写入头像出错");
            }
        }else{
            //否则使用默认头像。即将默认的头像复制到该用户目录下
            FileOptionUtil.asyncCopyFile(user.getUserAvatar(),User.RESOURCE_ROOT_DIR +"defaultAvatar.png");
        }


        //如果有上传人脸图片，则认为该用户要开启人脸识别
        MultipartFile faceMulFile = registerParam.getFaceFile();
        if(faceMulFile!=null){
            String faceToken = null;
            try {
                String faceBase64 = Base64.getEncoder().encodeToString(faceMulFile.getBytes());
                //人脸唯一标识
                faceToken = faceRecognitionService.detectFaceToken(faceBase64).get(0);
                user.setUserFaceToken(faceToken);
                //由于该平台不允许短时间内频繁访问
                Thread.sleep(1500);
                //给该人脸添加人脸标识
                faceRecognitionService.userIdentificationFace(user.getUserAccount(),faceToken);
                //由于该平台不允许短时间内频繁访问
                Thread.sleep(1000);
                //将该人脸添加到人脸库中
                faceRecognitionService.addFaceToSet(faceToken);
            } catch (Exception e) {
                e.printStackTrace();
                String finalFaceToken = faceToken;
                CachedThreadPoolUtil.submit(()->faceRecognitionService.removeFaceToSet(finalFaceToken));
                throw new RuntimeException("User模块的UserServiceImpl的register人脸标识出错");
            }
        }

        //声明创建一个该用户专属的消息队列，专门存放该用户的消息
        rabbitTemplate.execute(channel -> UserMessageComponent.queueDeclareAndBind(channel,user.getUserId()));

        //将该用户保存到数据库中
        userMapper.insert(user);
        //将用户的信息保存到redis中,因为接下来可能会去登录
        CacheUser(user,300);
        return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
    }

    @Transactional
    @Override
    public ServiceReturn<Boolean> enableFaceRecognition(User user,String faceBase64){
        if(user.getUserFaceToken()!=null){
            return new ServiceReturn<>(ServiceStateCode.ALREADY_ENABLE,false);
        }
        String faceToken = null;
        try {
            //校验该人脸是否已经被标识过了，即有其他用户用该人脸标识该用户
            FaceRecognitionReturnResult recognitionResult = faceRecognitionService.faceRecognition(faceBase64);
            if(recognitionResult !=null && recognitionResult.getConfidence()>=70){
                return new ServiceReturn<>(ServiceStateCode.FACE_ALREADY_BOUND,false);
            }

            faceToken = faceRecognitionService.detectFaceToken(faceBase64).get(0);
            if(faceRecognitionService.userIdentificationFace(user.getUserAccount(),faceToken)
                    && faceRecognitionService.addFaceToSet(faceToken)){
            }else{
                faceRecognitionService.removeFaceToSet(faceToken);
                return new ServiceReturn<>(ServiceStateCode.FAILURE,false);
            }


            if(user.getUserFaceToken()!=null){
                String finalUserFaceToken = user.getUserFaceToken();
                CachedThreadPoolUtil.submit(()-> faceRecognitionService.removeFaceToSet(finalUserFaceToken));
            }
            String finalFaceToken = faceToken;
            Long finalUserId = user.getUserId();
            CachedThreadPoolUtil.submit(()-> userMapper.updateFieldById(finalUserId,"user_face_token",finalFaceToken));
            user.setUserFaceToken(faceToken);
            redisClient.set(RedisKey.USER_INFO.getKey()+user.getUserId(),user);
        } catch (Exception e) {
            e.printStackTrace();
            String finalFaceToken = faceToken;
            CachedThreadPoolUtil.submit(()->faceRecognitionService.removeFaceToSet(finalFaceToken));
            return new ServiceReturn<>(ServiceStateCode.FAILURE,false);
        }
        return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
    }

    @Transactional
    @Override
    public ServiceReturn<Boolean> banFaceRecognition(User user){
        String faceToken = user.getUserFaceToken();
        if(faceToken!=null){
            Long finalUserId = user.getUserId();
            CachedThreadPoolUtil.submit(()->{
                userMapper.updateFieldById(finalUserId,"user_face_token",null);
                try {
                    faceRecognitionService.removeFaceToSet(faceToken);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            user.setUserFaceToken(null);
            redisClient.set(RedisKey.USER_INFO.getKey()+user.getUserId(),user);
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.ALREADY_DISABLE,false);
    }

    /**
     * 成功登录后进行的处理
     * @param user 登录成功的用户信息
     */
    public void loginDeal(User user){
        user.setUserState(UserState.online.getState());
        //从数据库中查询该用户浏览历史，并统计其商品偏向,并将商品偏向保存到redis中
        CachedThreadPoolUtil.submit(()->{
            userMapper.updateFieldById(user.getUserId(),"user_state", String.valueOf(UserState.online.getState()));
            //从数据库中查询该用户浏览历史。并统计其商品偏向
            Set<GoodsTypeFondRatio> fondRatioList = fondRatioDao.statisticsBrowseGoodType(user.getUserId());
            if(!fondRatioList.isEmpty()){
                Set<ZSetOperations.TypedTuple<Object>> set = new HashSet<>();
                fondRatioList.forEach(t ->{
                    set.add(new DefaultTypedTuple<Object>(t, (double) t.getNum()));
                });
                //将该用户的商品偏向保存到redis中，以便用于爱好推荐
                //如果set为空会报错
                redisClient.zsAdd(RedisKey.ZRange.USER_FOND_GOODS_RECOMMEND.getKey()+user.getUserId(),set);
            }
        });
        //将该用户信息，以k-v： 用户id 用户信息，存入redis中。
        CacheUser(user,-1);
    }

    @Override
    public ServiceReturn<User> faceRecognitionLogin(String faceBase64) {
        FaceRecognitionReturnResult result = faceRecognitionService.faceRecognition(faceBase64);
        if(result!=null){
            //识别结果的可信度超过70%,则认为是该人
            if(result.getConfidence()>=70){
                User user = (User) redisClient.get(RedisKey.USER_INFO +result.getUserId());
                if(user==null){
                    QueryWrapper<User> query = new QueryWrapper<>();
                    //注意：人脸识别中的userId是账户
                    query.eq("user_account",result.getUserId());
                    user = userMapper.selectOne(query);
                }
                loginDeal(user);
                return new ServiceReturn<>(ServiceStateCode.SUCCESS,user);
            }
        }
        return new ServiceReturn<>(ServiceStateCode.UNABLE_RECOGNITION,null);
    }



    @Override
    public ServiceReturn<User> login(String account, String password) {
        //如果redis缓存中有该账户对应的用户信息，则直接从缓存里拿,没有则去数据库查，并保存到redis中
        User user =  redisClient.getValueAsKey(RedisKey.USER_ID +account,RedisKey.USER_INFO.getKey(),User.class);
        if(user==null){
            QueryWrapper<User> queryParam = new QueryWrapper<>();
            queryParam.eq("user_account",account);
            user = userMapper.selectOne(queryParam);
        }

        //如果用户存在，则比较密码是否正确
        if(user!=null){
            if(user.getUserPassword().equals(password)){
                loginDeal(user);
                return new ServiceReturn(ServiceStateCode.SUCCESS,user);
            }
            return new ServiceReturn(ServiceStateCode.ACCOUNT_PASSWORD_NO_MATCH,null);
        }
        return new ServiceReturn(ServiceStateCode.NO_EXIST_USER,null);
    }

    @Override
    public ServiceReturn<User> emailLogin(String to, String authCode) {
        return authCodeCheck(to,authCode);
    }

    @Override
    public ServiceReturn<User> smsLogin(String phoneNumber,String authCode) {
        return authCodeCheck(phoneNumber, authCode);
    }

    @Override
    public ServiceReturn<Boolean> sendLoginAuthCodeToEmail(TemplateMail templateMail, String authCode) {
        String to = templateMail.getTo();
        //5分钟内，用户发送邮件，没必要次次去查
        //如果不存在手机号k-v，则进行缓存
        User user = redisClient.getValueAsKey(RedisKey.USER_ID + to,RedisKey.USER_INFO.getKey(), User.class);
        if(user==null){
            //如果第一次发送邮件，拿不到用户用户信息
            //从数据库中查找该邮件所对应的用户信息
            QueryWrapper<User> queryParam = new QueryWrapper<>();
            queryParam.eq("user_email",to);
            user = userMapper.selectOne(queryParam);
            //如果查不到该用户
            if(user == null){
                //说明邮件，在本网站是没有注册过的，所以不能发送邮件
                return new ServiceReturn<>(ServiceStateCode.NO_EXIST_USER,false);
            }
            //5分钟内，用户发送邮件，没必要次次去查
            redisClient.setEx(RedisKey.USER_ID +to, user.getUserId(),300);
            redisClient.setEx(RedisKey.USER_INFO.getKey()+user.getUserId(), user,300);
        }

        //发送模板邮件
        boolean sendState = mailService.sendTemplateMail(templateMail);
        if(sendState){
            //将登录授权码、用户信息保存到redis中
            redisClient.setEx(RedisKey.USER_LOGIN_CODE +to,authCode,120);
            //对用户信息进行延时(可以修改限时为永久，但是反过来不行)
            if(redisClient.getExpire(RedisKey.USER_ID +to)!=-1){
                redisClient.expire(RedisKey.USER_ID +to,300);
                redisClient.expire(RedisKey.USER_INFO.getKey()+user.getUserId(),300);
            }
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }


    @Override
    public ServiceReturn<Boolean> sendLoginAuthCodeToPhone(ShortMessage shortMessage, String authCode) {

        String phoneNumber = shortMessage.getAcceptPhoneNumber();
        //5分钟内，用户发送邮件，没必要次次去查
        //如果不存在手机号k-v，则进行缓存
        User user = redisClient.getValueAsKey(RedisKey.USER_ID + phoneNumber,RedisKey.USER_INFO.getKey(), User.class);
        if(user==null){
            //如果第一次发送短信到指定手机号
            //从数据库中查找该邮件所对应的用户信息
            QueryWrapper<User> queryParam = new QueryWrapper<>();
            queryParam.eq("user_phone",phoneNumber);
            user = userMapper.selectOne(queryParam);
            //如果查到该用户
            if(user == null){
                //说明手机号，在本网站是没有注册过的，所以不能发送短信
                return new ServiceReturn<>(ServiceStateCode.NO_EXIST_USER,false);
            }
            //5分钟内，用户发送短信，没必要次次去查
            redisClient.setEx(RedisKey.USER_ID +phoneNumber, user.getUserId(),300);
            redisClient.setEx(RedisKey.USER_INFO.getKey()+user.getUserId(), user,300);
        }
        //发送短信
        SendSmsResponseBody responseBody = smsService.sendSms(shortMessage);

        if("OK".equals(responseBody.getCode())){
            //将登录授权码、用户信息保存到redis中
            redisClient.setEx(RedisKey.USER_LOGIN_CODE +phoneNumber,authCode,120);
            //对用户信息进行延时(可以修改限时为永久，但是反过来不行)
            if(redisClient.getExpire(RedisKey.USER_ID +phoneNumber)!=-1){
                redisClient.expire(RedisKey.USER_ID +phoneNumber,300);
                redisClient.expire(RedisKey.USER_INFO.getKey()+user.getUserId(),300);
            }
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }


    @Override
    public ServiceReturn<Boolean> sendBindAuthCodeToEmail(TemplateMail templateMail, String authCode) {
        String to = templateMail.getTo();
        QueryWrapper<User> queryParam = new QueryWrapper<>();
        queryParam.eq("user_email",to);
        User user = userMapper.selectOne(queryParam);
        //如果查到该用户
        if(user != null){
            //因为接下来该用户可能会去登录该用户
            CacheUser(user,300);
            //说明邮件，在本网站是有注册过的，所以不能绑定
            return new ServiceReturn<>(ServiceStateCode.USER_EMAIL_ALREADY_BOUND,false);
        }
        //发送模板邮件
        boolean sendState = mailService.sendTemplateMail(templateMail);
        if(sendState){
            //将绑定授权码、用户信息保存到redis中
            redisClient.setEx(RedisKey.USER_BIND_CODE +to,authCode,120);
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }

    @Override
    public ServiceReturn<Boolean> sendBindAuthCodeToPhone(ShortMessage shortMessage, String authCode) {
        String phone = shortMessage.getAcceptPhoneNumber();
        QueryWrapper<User> queryParam = new QueryWrapper<>();
        queryParam.eq("user_phone",phone);
        User user = userMapper.selectOne(queryParam);
        //如果查到该用户
        if(user != null){
            //因为接下来该用户可能会去登录该用户
            CacheUser(user,300);
            //说明邮件，在本网站是有注册过的，所以不能绑定
            return new ServiceReturn<>(ServiceStateCode.PHONE_ALREADY_BOUND,false);
        }

        //发送模板邮件
        SendSmsResponseBody responseBody = smsService.sendSms(shortMessage);
        if("OK".equals(responseBody.getCode())){
            //将绑定授权码、用户信息保存到redis中
            redisClient.setEx(RedisKey.USER_BIND_CODE +phone,authCode,120);
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }

    @Override
    public ServiceReturn<Boolean> sendUnbindAuthCodeToEmail(TemplateMail templateMail, String authCode) {
        String email = templateMail.getTo();
        //发送模板邮件
        boolean sendState = mailService.sendTemplateMail(templateMail);
        if(sendState){
            //将解绑授权码、用户信息保存到redis中
            redisClient.setEx(RedisKey.USER_UNBIND_CODE +email,authCode,120);
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }

    @Override
    public ServiceReturn<Boolean> sendUnbindAuthCodeToPhone(ShortMessage shortMessage, String authCode) {
        String phoneNumber = shortMessage.getAcceptPhoneNumber();
        //发送模板邮件
        SendSmsResponseBody responseBody = smsService.sendSms(shortMessage);
        if("OK".equals(responseBody.getCode())){
            //将解绑授权码保存到redis中
            redisClient.setEx(RedisKey.USER_UNBIND_CODE +phoneNumber,authCode,120);
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }

    @Override
    public ServiceReturn<Boolean> sendChangePasswordToEmail(TemplateMail templateMail, String authCode) {
        String to = templateMail.getTo();
        User user = redisClient.getValueAsKey(RedisKey.USER_ID + to,RedisKey.USER_INFO.getKey(), User.class);
        if(user==null){
            QueryWrapper<User> query = new QueryWrapper<>();
            query.eq("user_email",to);
            user = userMapper.selectOne(query);
            if(user==null){
                return new ServiceReturn<>(ServiceStateCode.NO_EXIST_USER,false);
            }
            redisClient.setEx(RedisKey.USER_INFO.getKey()+user.getUserId(), user,300);
            redisClient.setEx(RedisKey.USER_ID +to, user.getUserId(),300);
        }
        //发送模板邮件
        boolean sendState = mailService.sendTemplateMail(templateMail);
        if(sendState){
            //将修改密码的授权码保存到redis中
            redisClient.setEx(RedisKey.USER_CHANGE_PASSWORD_CODE +to,authCode,120);
            if(redisClient.getExpire(RedisKey.USER_ID +to)!=-1){
                redisClient.expire(RedisKey.USER_INFO.getKey()+user.getUserId(),300);
                redisClient.expire(RedisKey.USER_ID +to,300);
            }
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }

    @Override
    public ServiceReturn<Boolean> sendChangePasswordToPhone(ShortMessage shortMessage, String authCode) {
        String phoneNumber = shortMessage.getAcceptPhoneNumber();
        User user = redisClient.getValueAsKey(RedisKey.USER_ID + phoneNumber, RedisKey.USER_INFO.getKey(), User.class);
        if(user==null){
            QueryWrapper<User> query = new QueryWrapper<>();
            query.eq("user_phone",phoneNumber);
            user = userMapper.selectOne(query);
            if(user==null){
                return new ServiceReturn<>(ServiceStateCode.NO_EXIST_USER,false);
            }
            redisClient.setEx(RedisKey.USER_INFO.getKey()+user.getUserId(), user,300);
            redisClient.setEx(RedisKey.USER_ID +phoneNumber, user.getUserId(),300);
        }
        SendSmsResponseBody responseBody = smsService.sendSms(shortMessage);
        if("OK".equals(responseBody.getCode())){
            //将修改密码的授权码保存到redis中
            redisClient.setEx(RedisKey.USER_CHANGE_PASSWORD_CODE +phoneNumber,authCode,120);
            if(redisClient.getExpire(RedisKey.USER_ID +phoneNumber)!=-1){
                redisClient.expire(RedisKey.USER_INFO.getKey()+user.getUserId(),300);
                redisClient.expire(RedisKey.USER_ID +phoneNumber,300);
            }
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }

    @Validated
    public ServiceReturn<Boolean> sendCancellationToEmail(TemplateMail mail,String authCode){
        String to = mail.getTo();
        User user = redisClient.getValueAsKey(RedisKey.USER_ID + to,RedisKey.USER_INFO.getKey(), User.class);
        if(user==null){
            return new ServiceReturn<>(ServiceStateCode.USER_NOT_BIND_EMAIL,false);
        }
        //发送模板邮件
        boolean sendState = mailService.sendTemplateMail(mail);
        if(sendState){
            //将修改密码的授权码保存到redis中
            redisClient.setEx(RedisKey.CANCELLATION_CODE.getKey() + user.getUserId(),authCode,120);
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }

    @Override
    public ServiceReturn<Boolean> sendCancellationToPhone(ShortMessage shortMessage, String authCode) {
        String phoneNumber = shortMessage.getAcceptPhoneNumber();
        User user = redisClient.getValueAsKey(RedisKey.USER_ID + phoneNumber, RedisKey.USER_INFO.getKey(), User.class);
        if(user==null){
            return new ServiceReturn<>(ServiceStateCode.USER_NOT_BIND_PHONE,false);
        }
        SendSmsResponseBody responseBody = smsService.sendSms(shortMessage);
        if("OK".equals(responseBody.getCode())){
            //将修改密码的授权码保存到redis中
            redisClient.setEx(RedisKey.CANCELLATION_CODE.getKey()+user.getUserId(),authCode,120);
            return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
        }
        return new ServiceReturn<>(ServiceStateCode.SEND_FAILURE,false);
    }


    @Override
    public ServiceReturn<String> bindEmail(User user,String to, String authCode) {
        //进行邮箱绑定
        return bindOption(user, "userEmail", to, authCode);
    }

    @Override
    public ServiceReturn<String> bindPhone(User user, String phone, String authCode) {
        //再次进行绑定
        return bindOption(user, "userPhone", phone, authCode);
    }

    @Override
    public ServiceReturn<Boolean> unbindEmail(User user, String authCode) {
        return unbindOption(user,"userEmail",authCode);
    }

    @Override
    public ServiceReturn<Boolean> unbindPhone(User user, String authCode) {
        return unbindOption(user,"userPhone",authCode);
    }

    @Override
    public ServiceReturn<Boolean> loginAfterChangePassword(User user,String oldPassword , String newPassword) {
        if(user.getUserPassword().equals(oldPassword)){
            try {
                userMapper.updateFieldById(user.getUserId(), "user_password", newPassword);
                //更新session中密码的值
                user.setUserPassword(newPassword);
                //更新redis中用户信息的缓存
                redisClient.set(RedisKey.USER_INFO.getKey() + user.getUserId(),user);
                return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
            } catch (Exception e) {
                e.printStackTrace();
                return new ServiceReturn<>(ServiceStateCode.FAILURE,false);
            }
        }
        return new ServiceReturn<>(ServiceStateCode.OLD_PASSWORD_NOT_INCORRECT,false);

    }

    @Override
    public ServiceReturn<Boolean> notLoginChangePassword(String fieldValue,String newPassword){
        //删除修改密码的授权码
        redisClient.delKey(RedisKey.USER_CHANGE_PASSWORD_CODE +fieldValue);
        //只要验证码不过期，则一定user不过期
        User user = redisClient.getValueAsKey(RedisKey.USER_ID + fieldValue, RedisKey.USER_INFO.getKey(), User.class);
        userMapper.updateFieldById(user.getUserId(), "user_password", newPassword);

        //对缓存中的信息进行更新
        user.setUserPassword(newPassword);
        if(redisClient.getExpire(RedisKey.USER_ID +fieldValue)!=-1){
            redisClient.setEx(RedisKey.USER_INFO.getKey()+user.getUserId(),user,300);
            redisClient.setEx(RedisKey.USER_ID +fieldValue,user.getUserId(),300);
        }else{
            redisClient.set(RedisKey.USER_INFO.getKey()+user.getUserId(),user);
        }
        return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
    }

    @Override
    public ServiceReturn<Boolean> checkChangePasswordAuthCode(String checkFieldValue, String authCode) {
        String checkCode = (String) redisClient.get(RedisKey.USER_CHANGE_PASSWORD_CODE + checkFieldValue);
        if(checkCode!=null){
            if(checkCode.equals(authCode)){
                return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
            }
            return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_NOT_CORRECT,false);
        }
        return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_TIMEOUT,false);
    }

    /**
     * 解绑操作
     * @param user 解绑的用户信息
     * @param fieldName 解绑字段名，比如：解绑邮箱，则是userEmail
     * @param authCode 授权码
     * @return 返回解绑状态
     */
    @Transactional
    public ServiceReturn<Boolean> unbindOption(User user,String fieldName,String authCode){
        try{
            //利用反射获取该字段
            Field field = User.class.getDeclaredField(fieldName);
            field.setAccessible(true);
            //获取绑定前的值
            String unbindBeforeValue = (String) field.get(user);
            //获取redis中k-v中手机号或者邮件对应的解绑授权码
            String checkCode = (String) redisClient.get(RedisKey.USER_UNBIND_CODE +unbindBeforeValue);
            //判断验证码是否还存在
            if(checkCode!=null) {
                //如果验证码存在，则校验验证码是否正确
                if (checkCode.equals(authCode)) {
                    //移除解绑验证码
                    redisClient.delKey(RedisKey.USER_UNBIND_CODE +unbindBeforeValue);

                    TableField tableField = field.getAnnotation(TableField.class);
                    String dbField = tableField.value();
                    //更新user表里的该用户的邮箱或者手机号
                    userMapper.updateFieldById(user.getUserId(),dbField,null);
                    //删除redis缓存中原本的邮箱或者手机号缓存
                    redisClient.delKey(RedisKey.USER_ID +unbindBeforeValue);
                    //将当前登录的session的用户，进行信息的更新
                    field.set(user,null);
                    //更新一下redis缓存中的用户信息
                    redisClient.set(RedisKey.USER_INFO.getKey()+user.getUserId(),user);
                    return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
                }
                return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_NOT_CORRECT,false);
            }
            return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_TIMEOUT,false);
        }catch (Exception e){
            e.printStackTrace();
            return new ServiceReturn<>(ServiceStateCode.FAILURE,false);
        }
    }


    /**
     * 进行邮箱或者手机号的绑定
     * @param user 原本的用户信息
     * @param fieldName 要更新的字段。比如：手机号
     * @param filedNewValue 更新字段的更新值，比如：新绑定的手机号
     * @param authCode 绑定验证码
     * @return 返回绑定的信息
     */
    @Transactional
    public ServiceReturn<String> bindOption(User user,String fieldName,String filedNewValue,String authCode){
        //获取redis中k-v中手机号或者邮件对应的绑定授权码
        String checkCode = (String) redisClient.get(RedisKey.USER_BIND_CODE +filedNewValue);
        //判断验证码是否还存在
        if(checkCode!=null){
            //如果验证码存在，则校验验证码是否正确
            if (checkCode.equals(authCode)) {
                try {
                    //移除绑定验证码
                    redisClient.delKey(RedisKey.USER_BIND_CODE +filedNewValue);

                    //利用反射获取该字段
                    Field field = User.class.getDeclaredField(fieldName);
                    field.setAccessible(true);
                    String originalValue = (String) field.get(user);


                    TableField tableField = field.getAnnotation(TableField.class);
                    String dbField = tableField.value();
                    //更新user表里的该用户的邮箱或者手机号
                    userMapper.updateFieldById(user.getUserId(),dbField,filedNewValue);
                    //删除redis缓存中原本的邮箱或者手机号缓存
                    redisClient.delKey(RedisKey.USER_ID +originalValue);
                    //将当前登录的session的用户，进行信息的更新
                    field.set(user,filedNewValue);
                    //对redis里的用户进行更新
                    redisClient.set(RedisKey.USER_INFO.getKey()+user.getUserId(),user);
                    //并缓存新手机号或者邮箱的缓存信息
                    redisClient.set(RedisKey.USER_ID +filedNewValue,user.getUserId());
                } catch (Exception e) {
                    e.printStackTrace();
                    return new ServiceReturn<>(ServiceStateCode.FAILURE,null);
                }
                return new ServiceReturn<>(ServiceStateCode.SUCCESS,filedNewValue);
            }
            return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_NOT_CORRECT,null);
        }
        return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_TIMEOUT,null);
    }


    /**
     * 校验验证码是否与发送的验证码一致
     * @param checkField 校验字段，比如：校验字段：手机号,则authCode:login:手机号代表：手机号登录验证码、authCode:bind:邮箱代表邮箱绑定验证码
     * @param authCode 验证码
     * @return 返回校验结果
     */
    public ServiceReturn<User> authCodeCheck(String checkField, String authCode){
        //获取redis中k-v中phoneNumber手机号邮件对应的登录授权码
        String checkCode = (String) redisClient.get(RedisKey.USER_LOGIN_CODE +checkField);
        //判断验证码是否还存在
        if(checkCode!=null){
            //如果验证码存在，则校验验证码是否正确
            if (checkCode.equals(authCode)) {
                redisClient.delKey(RedisKey.USER_LOGIN_CODE +checkField);
                //只要验证码不过期，则该手机号对应的用户信息，一定还没过期
                User user =  redisClient.getValueAsKey(RedisKey.USER_ID +checkField,RedisKey.USER_INFO.getKey(),User.class);
                loginDeal(user);
                return new ServiceReturn<>(ServiceStateCode.SUCCESS,user);
            }
            return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_NOT_CORRECT,null);
        }
        return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_TIMEOUT,null);
    }


    @Transactional
    @Override
    public ServiceReturn<User> updateUserInfo(User user,UserPersonalInfoParam updateInfo) {
        MultipartFile avatarMulFile = updateInfo.getUserAvatar();
        CachedThreadPoolUtil.submit(()->{
            if(avatarMulFile!=null){
                try {
                    FileOutputStream writer = new FileOutputStream(user.getUserAvatar());
                    writer.write(avatarMulFile.getBytes());
                    writer.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        if(updateInfo.getUserName()!=null){
            //如果为空字符串，在更新时，会自动填充
            user.setUserName(updateInfo.getUserName());
        }
        if(updateInfo.getUserArea()!=null){
            user.setUserArea(updateInfo.getUserArea());
        }
        if(updateInfo.getUserSex()!=null){
            user.setUserSex(updateInfo.getUserSex().getSex());
        }
        if(updateInfo.getUserDateBirth()!=null){
            user.setUserDateBirth(updateInfo.getUserDateBirth());
        }
        if(updateInfo.getUserSignature()!=null){
            //如果为空字符串，在更新时，会自动填充
            user.setUserSignature(updateInfo.getUserSignature());
        }
        UserQueryParam.Builder builder = UserQueryParam.builder();
        builder.setUserId(user.getUserId());
        int updateRow = userPersonalInfoParamDao.updateUser(updateInfo,builder.build());
        //更新redis缓存
        redisClient.set(RedisKey.USER_INFO.getKey()+user.getUserId(),user);
        return new ServiceReturn<>(ServiceStateCode.SUCCESS,user);
    }


    /**
     * 将用户常用的用户信息，长时间保存起来
     * @param user 要保持到redis中的用户数据
     * @param time 要保持的时间，单位是秒。如果为-1代表永久缓存
     */
    public void CacheUser(User user,int time){
        CachedThreadPoolUtil.submit(()->{
            Long userId = user.getUserId();
            if(time == -1){
                redisClient.set(RedisKey.USER_INFO.getKey()+userId,user);
                redisClient.set(RedisKey.USER_ID +user.getUserAccount(),userId);
                if(user.getUserPhone()!=null){
                    redisClient.set(RedisKey.USER_ID +user.getUserPhone(),userId);
                }
                if(user.getUserEmail()!=null){
                    redisClient.set(RedisKey.USER_ID +user.getUserEmail(),userId);
                }
            }else{
                redisClient.setEx(RedisKey.USER_INFO.getKey()+userId,user,time);
                redisClient.setEx(RedisKey.USER_ID +user.getUserAccount(),userId,time);
                if(user.getUserPhone()!=null){
                    redisClient.setEx(RedisKey.USER_ID +user.getUserPhone(),userId,time);
                }
                if(user.getUserEmail()!=null){
                    redisClient.setEx(RedisKey.USER_ID +user.getUserEmail(),userId,time);
                }
            }
        });
    }

    @Override
    public void loginOut(User nowLoginUser) {
        //设置用户为离线状态
        nowLoginUser.setUserState(UserState.offline.getState());
        CachedThreadPoolUtil.submit(()->{
            //更新用户表
            userMapper.updateFieldById(nowLoginUser.getUserId(),"user_state", String.valueOf(UserState.offline.getState()));
            //删除该用户在哪个session登录着
            redisClient.hDel(RedisKey.HashMap.USER_LOGIN_IN_SESSION_ID.getKey(),String.valueOf(nowLoginUser.getUserId()));

            redisClient.delKey(RedisKey.USER_INFO.getKey()+nowLoginUser.getUserId());
            redisClient.delKey(RedisKey.USER_ID +nowLoginUser.getUserAccount());
            if(nowLoginUser.getUserPhone()!=null){
                redisClient.delKey(RedisKey.USER_ID +nowLoginUser.getUserPhone());
            }
            if(nowLoginUser.getUserEmail()!=null){
                redisClient.delKey(RedisKey.USER_ID +nowLoginUser.getUserEmail());
            }
        });
    }

    @Transactional
    @Override
    public ServiceReturn<Boolean> cancellationByPassword(User user, String password){
        if(user.getUserPassword().equals(password)){
            return cancellationOption(user);
        }
        return new ServiceReturn<>(ServiceStateCode.ACCOUNT_PASSWORD_NO_MATCH,false);
    }

    @Transactional
    @Override
    public ServiceReturn<Boolean> cancellationByAuthCode(User user,String authCode) {
        String checkCode = (String) redisClient.get(RedisKey.CANCELLATION_CODE.getKey() + user.getUserId());
        //判断验证码是否还存在
        if(checkCode!=null){
            //如果验证码存在，则校验验证码是否正确
            if (checkCode.equals(authCode)) {
                return cancellationOption(user);
            }
            return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_NOT_CORRECT,false);
        }
        return new ServiceReturn<>(ServiceStateCode.AUTH_CODE_TIMEOUT,false);
    }

    /**
     * 注销账户操作
     * @param user 注销用户信息
     * @return 返回注销状态
     */
    public ServiceReturn<Boolean> cancellationOption(User user){
        //删除掉注销验证码
        redisClient.delKey(RedisKey.CANCELLATION_CODE.getKey() + user.getUserId());
        //删除所有有关于该用户的信息
        userMapper.deleteAllInfoAboutUser(user.getUserId());
        //将用户进行登出，并清空redis缓存，一定要在删除表之后清除redis缓存，不然会有可能导致清除后又在没删除用户之前登录上去，有缓存了
        loginOut(user);
        //删除该用户的消息队列
        rabbitTemplate.execute(channel -> channel.queueDelete(UserMessageComponent.QUEUE_PRE+user.getUserId(),false,false));
        //删除人脸库中该用户的所有人脸信息
        if(user.getUserFaceToken()!=null){
            try {
                faceRecognitionService.removeFaceToSet(user.getUserFaceToken());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //删除该用户目录下的所有资源
        FileOptionUtil.asyncDeleteDir(user.getUserResourceDir(),true);
        return new ServiceReturn<>(ServiceStateCode.SUCCESS,true);
    }
}
